/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.util;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.zip.GZIPInputStream;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.constants.SignConstants;

/**
 * 流工具类，注意，本类中的方法都没有对输入输出流作flush和close操作，需要业务层实现
 *
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
@AnAlias("Streams")
public class Streams implements CodeConstants, SignConstants
{
    /******************************************************************/
    //Stream与Reader
    /******************************************************************/
    
    /** 把Stream转化为Reader */
    public static BufferedReader toReader(InputStream input, String encoding) throws UnsupportedEncodingException
    {
        return new BufferedReader(new InputStreamReader(input, encoding));
    }
    
    /** 把Stream转化为Reader */
    public static BufferedReader toReader(InputStream input, Charset charset) throws UnsupportedEncodingException
    {
        return new BufferedReader(new InputStreamReader(input, charset));
    }
    
    /** 把Stream转化为Reader编码为UTF-8 */
    public static BufferedReader toReaderUTF8(InputStream input)
    {
        return new BufferedReader(new InputStreamReader(input, _UTF_8_C_));
    }
    
    /******************************************************************/
    //Stream与String
    /******************************************************************/
    
    /** 把Stream化为Reader，再读成UTF-8字符串 */
    public static String getStringUTF8(InputStream stream) throws IOException
    {
        return getString(stream, _UTF_8_C_);
    }

    /** 把Stream化为Reader，再读成GBK字符串 */
    public static String getStringGBK(InputStream stream) throws IOException
    {
        return getString(stream, _GBK_C_);
    }
    
    /** 把Stream化为Reader，再读成字符串 */
    public static String getString(InputStream input, String encoding) throws IOException
    {
        Charset charset = Charset.forName(encoding);
        return getString(input, charset);
    }
    
    /** 把Stream化为Reader，再读成字符串 */
    public static String getString(InputStream input, Charset charset) throws IOException
    {
        Asserts.notNull(input, _INPUT_);
        Asserts.notNull(charset, _CHARSET_);
        
        try
        {
            BufferedReader reader = toReader(input, charset);
            StringWriter writer = new StringWriter();

            char[] buffer = new char[KiB];
            int len = 0;
            while ((len = reader.read(buffer)) > 0)
            {
                writer.write(buffer, 0, len);
            }

            return writer.toString();
        }
        finally
        {
            Closes.closeIgnoreException(input);
        }
    }
    
    /**对Gzip的流进行解析读取字符串 */
    public static String getStringGzip(InputStream input, String encoding) throws IOException
    {
        Asserts.notNull(input, _INPUT_);
        
        GZIPInputStream gzip = new GZIPInputStream(input);
        
        try
        {
            return getString(gzip, encoding);
        }
        finally
        {
            Closes.closeIgnoreException(gzip, input);
        }
    }
    
    /**
     * 字符串转流，GBK编码，内容为null时返回null
     * 
     * @param content   内容
     * @return          ByteArrayInputStream
     */
    public static ByteArrayInputStream toStreamGBK(String content)
    {
        return toStream(content, _GBK_C_);
    }
    
    /** 字符串转流，UTF-8编码，内容为null时返回null */
    public static ByteArrayInputStream toStreamUTF8(String content)
    {
        return toStream(content, _UTF_8_C_);
    }
    
    /** 字符串转流，内容为null时返回null */
    public static ByteArrayInputStream toStream(String content, String encoding)
    {
        if (content == null)
            return null;
        
        try
        {
            return new ByteArrayInputStream(content.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e)
        {
            throw Asserts.exception("非法的字符编码["+encoding+"]", e);
        }
    }
    
    /** 字符串转流，内容为null时返回null */
    public static ByteArrayInputStream toStream(String content, Charset charset)
    {
        Asserts.notNull(content, "content");
        Asserts.notNull(charset, _CHARSET_);
        
        return new ByteArrayInputStream(content.getBytes(charset));
    }
    
    /******************************************************************/
    //Stream与byte
    /******************************************************************/
    
    /** 跳过输入流一部分 */
    public static void skip(InputStream input, long len) throws IOException
    {
        while (len > 0)
        {
            long count = input.skip(len);
            if (count < 0)
                throw new EOFException("按长度跳过流时,长度不够即到达流尾端");
            
            len -= count;
        }
    }
    
    /** 读输入流到缓存中 */
    public static void putBytes(InputStream input, byte[] buffer, int off, int len) throws IOException
    {
        while (len > 0)
        {
            int count = input.read(buffer, off, len);
            if (count < 0)
                throw new EOFException("按长度读流时,长度不够即到达流尾端");
            
            off += count;
            len -= count;
        }
    }
    
    /** 读输入流到输出流中 */
    public static long putBytes(InputStream input, OutputStream output) throws IOException
    {
        byte[] buffer = new byte[KiB];
        int len = 0;long count = 0;
        while ((len = input.read(buffer)) != -1)
        {
            output.write(buffer, 0, len);
            count += len;
        }
        return count;
    }
    
    /** 读CLASSPATH中资源到输出流中 */
    public static long putBytesClassPath(Class<?> clazz, String path, OutputStream output) throws IOException
    {
        return putBytes(clazz.getResourceAsStream(path), output);
    }
    
    /** 读流中字节数，建议只对数据量比较小的流采用该方法 */
    public static byte[] getBytes(InputStream input) throws IOException
    {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        putBytes(input, output);
        return output.toByteArray();
    }
    
    /** 读流中字节数 */
    public static byte[] getBytes(InputStream input, int len) throws IOException
    {
        byte[] buffer = new byte[len];
        putBytes(input, buffer, 0, len);
        
        return buffer;
    }

    /** 读文件中字节数 */
    public static byte[] getBytesFilePath(File file) throws IOException
    {
        if (file == null || !file.isFile() || !file.canRead())
            return null;

        try (FileInputStream input = new FileInputStream(file))
        {
            return getBytes(input);
        }
    }
    
    /** 读文件中字节数，异常返回null */
    public static byte[] getBytesFilePathExceptionNull(File file)
    {
        try{return getBytesFilePath(file);}catch (Throwable e){return null;}
    }
    
    /** 读CLASSPATH中资源 */
    public static byte[] getBytesClassPath(String path)
    {
        InputStream input = Resources.getResourceStream(path);
        if (input == null)
            return null;
        
        try
        {
            return getBytes(input);
        }
        catch (Throwable e)
        {//异常返回null，读取失败
            return null;
        }
        finally
        {
            Closes.closeIgnoreException(input);
        }
    }
    
    /** 读CLASSPATH中资源，异常返回null */
    public static byte[] getBytesClassPathExceptionNull(String path)
    {
        try{return getBytesClassPath(path);}catch (Throwable e){return null;}
    }
    
    /** 读CLASSPATH中资源 */
    public static byte[] getBytesClassPath(Class<?> cls, String path) throws IOException
    {
        InputStream input = cls.getResourceAsStream(path);
        if (input == null)
            input = Resources.getResourceStream(path);
        
        if (input == null)
            return null;
        
        try
        {
            return getBytes(input);
        }
        finally
        {
            Closes.closeIgnoreException(input);
        }
    }
    
    /** 读CLASSPATH中资源，异常返回null */
    public static byte[] getBytesClassPathExceptionNull(Class<?> cls, String path)
    {
        try{return getBytesClassPath(cls, path);}catch (Throwable e){return null;}
    }
    
    /******************************************************************/
    //Stream读行和encoding
    /******************************************************************/

    /** 
     * 读流获取一行，指定最大长度，偏移量指定为0，按顺序读取
     * 
     * @param input 流
     * @param b 放进的缓存
     * @param len 支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int len) throws IOException
    {
        return readLine(input, b, 0, len);
    }
    
    /** 
     * 读流获取一行，指定偏移量和最大长度
     * 
     * @param input 流
     * @param b 放进的缓存
     * @param off 流字节偏移量
     * @param len 支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int off, int len) throws IOException
    {
        if (len <= 0)
            return 0;

        int count = 0, c;
        while ((c = input.read()) != -1)
        {
            b[off++] = (byte) c;
            count++;
            if (c == '\n' || count == len)
                break;
        }

        return count > 0 ? count : -1;
    }
    
    /**
     * 依次检测流的编码格式，该方法一定返回编码，如果有BOM则以BOM为准，否则判断是否是UTF-8，如果不是则是ANSI
     * 
     * 1.bytes有效长度<2取_UTF_8_
     * 2.有BOM，取BOM
     * 3.逐个字节检查是否符合UTF-8，直到结尾
     *      3.1 中途有不符合UTF-8的，返回系统编码Systems.getSystemEncoding();
     *      3.2 到结尾都符合UTF-8，返回UTF-8
     * 
     * @param bytes     字节数组
     * @param len       指定在bytes中有效的长度
     * @return          该方法一定返回编码
     */
    public static String getStreamEncoding(byte[] bytes, int len)
    {
        return getStreamEncoding(bytes, len, Systems.getSystemEncoding());
    }
    
    /**
     * 依次检测流的编码格式，该方法一定返回编码，如果有BOM则以BOM为准，否则判断是否是UTF-8，如果不是则是ANSI
     * 
     * 1.bytes有效长度<2取_UTF_8_
     * 2.有BOM，取BOM
     * 3.逐个字节检查是否符合UTF-8，直到结尾
     *      3.1 中途有不符合UTF-8的，返回缺省编码
     *      3.2 到结尾都符合UTF-8，返回UTF-8
     * 
     * @param bytes             字节数组
     * @param len               指定在bytes中有效的长度
     * @param defaultEncoding   缺省编码
     * @return                  该方法一定返回编码
     */
    public static String getStreamEncoding(byte[] bytes, int len, String defaultEncoding)
    {
        if (bytes == null || bytes.length < 2 || len < 2)
            return _UTF_8_;//null,0,1用UTF-8没问题
        
        //有BOM，以BOM为准
        String encoding = getStreamEncodingBOM(bytes);
        if (encoding != null)
            return encoding;
        
        //没BOM，逐个字节检查是否是UTF-8
        if (bytes.length < len)
            len = bytes.length;
        
        //UTF-8 有以下编码规则：11100101 10001000 10011010表示"刚"字,01101001表示"i"
        //如果高1位=0，表示这是一个 ASCII 字符（00 - 7F）。这里GBK,ISO8859-1,UTF-8相同。                        01101001 表示 "i"
        //如果高2位=10，表示它不是首字节，需要向前查找才能得到当前字符的首字节，一起组成一个字符。
        //如果高2位=11，表示它是字符首字节
        //             1)高3位=110表示该字符由2个字节组成，后一个字节高2位=10，如俄文字符等。            11010000 10111011 表示 "л"
        //             2)高3位=111表示该字符由3个字节组成，后两个字节高2位=10，如中文字符等。            11100101 10001000 10011010 表示 "刚"
        //
        encoding = defaultEncoding == null?Systems.getSystemEncoding():defaultEncoding;
        byte firstChar=0;int hasNum=0;int num=0;
        for (int i=0;i<len;i++)
        {
            byte b = bytes[i];
            if (Bits.isZero(b, 1))
            {//高1位=0的ANSI
                
                //测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum !=0 && hasNum != num)
                    return encoding;
                
                firstChar = 0;
                hasNum = 0;
                num = 0;
                continue;
            }
            
            //高1位=1时，判断高第2位
            if (Bits.isZero(b, 2))
            {
                //高第2位是0,表示前面有首字节，如果没有则测试未通过，返回本地编码
                if (firstChar == 0)
                    return encoding;
                
                //通过个数++
                num++;
            }
            else
            {//高第2位是1，表示首字节
                
                //测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum !=0 && hasNum != num)
                    return encoding;
                
                //否则测试通过，重新下一个
                firstChar = b;
                hasNum = (Bits.isZero(b, 3))?1:2;
                num = 0;
            }
        }
        
        //最后一个，测试时否满足UTF-8规范，不满足返回本地编码
        if (firstChar != 0 && hasNum !=0 && hasNum != num)
            return encoding;
        
        //全部测试通过，返回UTF-8
        return _UTF_8_;
    }
    
    /**
     * 根据指定的流检查是否有BOM头的编码格式
     * 
     * @param b 字节数组
     * @return UTF-8/UTF-16BE/UTF-16LE/UTF-32BE/UTF-32LE/如果都不是则为null
     */
    public static String getStreamEncodingBOM(byte[] b)
    {
        if (b == null || b.length < 2)
            return null;
        
        if (Bytes.isUTF8BOM(b))
            return _UTF_8_;
        else if (Bytes.isUTF16BEBOM(b))
            return _UTF_16BE_;
        else if (Bytes.isUTF16LEBOM(b))
            return _UTF_16LE_;
        else if (Bytes.isUTF32BEBOM(b))
            return _UTF_32BE_;
        else if (Bytes.isUTF32LEBOM(b))
            return _UTF_32LE_;
        
        return null;
    }
}
